/*
 * Cats and mouse.
 *
 * In this game, you are a mouse, who must eat all the cheese in a maze,
 * while avoiding the cats who inhabit the maze. Being an untidy mouse,
 * you leave a trail of crumbs wherever you go, which the cats will always
 * follow if they happen upon it (they know that one end of a trail of
 * crumbs ends at the mouse). The good news is that the cats are very
 * hungry, and will eat the crumbs as they follow the trail.
 *
 * Since they are so hungry, the cats will also eat the mouse (you!). Every
 * time a cat bites you you get weaker, and if you get bitten three times,
 * you will die, and be devoured.
 *
 * Like the cats, the mouse is always on the run. Use the arrow keys to set
 * the direction that the mouse tries to take. If you get tired of running,
 * you can press ESCAPE to end the game.
 *
 * As you complete each level, the maze gets larger, more cats appear, and
 * the cats move faster! ...
 *
 * Copyright 1998-2003 Dave Dunfield
 * All rights reserved.
 *
 * Permission granted for personal (non-commercial) use only.
 *
 * Compile command: CC CATMOUSE -pof
 */
#include <stdio.h>
#include <window.h>
#include <setjmp.h>

#define	V		25				/* Vertical dimension of maze */
#define	H		80				/* Horizontal dimension of maze */
#define	P		150				/* Delay between moves (ms) */

/* Maze information */
#define	EMPTY	0				/* Maze location is empty */
#define	CHEESE	1				/* Maze location contains cheese */
#define	TRAIL	2				/* Maze location contains crumbs */
#define	WALL	3				/* Maze location is a wall */

/* Display characters */
#define	DWALL	0xB1			/* Wall of maze */
#define	DCHEESE	0x107			/* Cheese */
#define	DTRAIL	0xFA			/* Trail */

/* Cat movement methods */
#define	R_HAND	0				/* Always choose right passage */
#define	L_HAND	1				/* Always choose Left passage */
#define	FR_HAND	2				/* Straight, with RH preference */
#define	FL_HAND	3				/* Straignt, with LH preference */

struct cat {
	int x, y;					/* Current position of cat */
	unsigned speed;				/* Speed at which cat moves */
	unsigned ticks;				/* Clock ticks for method change */
	unsigned stop;				/* Ticks to stop cat */
	unsigned char direction;	/* Direction (0=Up, 1=Right 2=Down, 3=Left) */
	unsigned char method;		/* Maze traverse method */
	char symbols[4] };			/* Characters to depict cat */

struct cat cat[] = {
	int 0, 0, 3, 0, 0, char 1, FR_HAND, '^',  '>',  'v',  '<'  ,
	int 0, 0, 2, 0, 0, char 2, L_HAND,  0x18, 0x1A, 0x19, 0x1B ,
	int 0, 0, 1, 0, 0, char 3, FL_HAND, 0xC1, 0xC3, 0xC2, 0xB4 ,
	int 0, 0, 3, 0, 0, char 0, L_HAND,  0xD0, 0xC6, 0xD2, 0xB5 ,
	int 0, 0, 2, 0, 0, char 1, FR_HAND, 0xCF, 0xC7, 0xD1, 0xB6 ,
	int 0, 0, 1, 0, 0, char 2, R_HAND,  0xCA, 0xCC, 0xCB, 0xB9 };

unsigned char
	maze[V+2][H+2];				/* Contains the maze */
int
	mx,							/* Mouses current X position */
	my,							/* Mouses current Y position */
	direction;					/* Mouses current direction */
unsigned
	cats = 1,					/* Number of cats */
	passages = 10,				/* Number of passages in maze */
	level,						/* Current operating level */
	num_cheese,					/* Number of cheese remaining */
	speed_tick,					/* Tick count for cat speed calculation */
	bites;						/* Number of bites remaining before death */
char
	quiet;						/* Quiet mode */
jmp_buf
	jbuffer;					/* Buffer for longjmp */
extern unsigned
	RAND_SEED;					/* Seed for pseudo-random generator */

/*
 * Get BIOS clock tick count
 */
get_ticks() asm
{
	MOV		AX,40h
	MOV		ES,AX
	MOV		AX,word ptr ES:[006ch]
}

/*
 * Main program.
 * Build the maze.
 * Accept keyboard input & set mouses pending direction.
 * Move mouse(player) and all cats.
 */
main()
{
	int i, k;

	wopen(0, 0, H, V, WSAVE|WCOPEN|NORMAL);
	wcursor_off();

	setjmp(jbuffer);

	k = 0;
	for(i=0; i < cats; ++i)
		k += cat[i].speed;
	++level;
	for(;;) {
		wclwin();
		wprintf("Level: %u : %u passages, %u cat%s, speed factor: %u, Sound: %s\n",
				level, passages, cats, "s"+(cats == 1), k / cats,
				quiet ? "OFF" : "ON");
		wprintf("ESC=Quit  SPACE=Sound  Enter=Continue ");
		switch(wgetc()) {
			case ' ' :
				quiet = !quiet;
			default:
				continue;
			case 0x1B :
				wclose();
				return;
			case '\n' : }
		break; }

	mx = 40;
	my = 12;
	direction = 2;
	bites = 3;

	/* Create & display the maze */
	for(i=0; i < V; ++i) {
		wgotoxy(0, i);
		for(k=0; k < H; ++k)
			wputc(DWALL); }
	RAND_SEED = get_ticks();
	memset(maze, WALL, sizeof(maze));
	build_maze();

	for(;;) {
		wgotoxy(76, 0); wprintf("%4u", num_cheese);
		switch(wtstc()) {
			case 0x1B:	wclose(); 		return;
			case ' ' :	quiet = !quiet;	continue;
			case _KUA:	direction = 0;	break;
			case _KRA:	direction = 1;	break;
			case _KDA:	direction = 2;	break;
			case _KLA:	direction = 3;	}
		speed_tick = (speed_tick + 1) & 3;
		move_mouse();
		for(i=0; i < cats; ++i)
			move_cat(cat[i]); }
}

/*
 * Generate the mase
 */
build_maze()
{
	int x, y, i, j;

	x = mx;
	y = my;
	for(j=0; j < passages; ++j) {
		if(j&1) {
			i = random(H-1);
			while(x <= i) {
				wgotoxy(x, y); wputc(DCHEESE);
				maze[y+1][x++ + 1] = CHEESE; }
			while(x >= i) {
				wgotoxy(x, y); wputc(DCHEESE);
				maze[y+1][x-- + 1] = CHEESE; }
			x = i; }
		else {
			i = random(V-1);
			while(y <= i) {
				wgotoxy(x, y); wputc(DCHEESE);
				maze[y++ + 1][x+1] = CHEESE; }
			while(y >= i) {
				wgotoxy(x, y); wputc(DCHEESE);
				maze[y-- + 1][x+1] = CHEESE; }
			y = i; }
	delay(50); }

	/*
	 * Compute total amount of cheese in maze, and deploy cats at the
	 * first non-wall location
	 */
	x = cats;
	for(num_cheese=i=0; i < V; ++i)
		for(j=0; j < H; ++j)
			if(maze[i+1][j+1] == CHEESE) {
				++num_cheese;
				while(x) {
					cat[--x].x = j;
					cat[x].y = i; } }
	--num_cheese;
}

/*
 * Mouse has been hit!
 */
mouse_hit()
{
	char *ptr;
	switch(--bites) {
		case 0 :
			wclwin();
			wgotoxy(27, 11); wprintf("***************************");
			wgotoxy(27, 12); wprintf("*** You just got eaten! ***");
			wgotoxy(27, 13); wprintf("***************************");
			beep(500, 1000);
			delay(250);
			beep(400, 1000);
			delay(250);
			beep(300, 1000);
			wclose();
			exit(0);
		case 1 : ptr = "Squeek.. Squeek.. Squeek.. I'm DYING here!"; break;
		default: ptr = "Ouch! That hurt!" ; }
	wgotoxy(0, 0);
	wprintf(ptr);
	beep(500, 1000);
}
	
/*
 * Move a single cat through the maze
 */
move_cat(struct cat *m)
{
	int x, y, ax, ay, lx, ly, bx, by, rx, ry, x1, y1, d, d1;

	if(m->stop) {
		--m->stop;
		return; }

	x = m->x;
	y = m->y;
	d = m->direction;

	if(speed_tick < m->speed)
		return;

	if(++m->ticks > 10) {
		m->method = (m->method + 1) & 3;
		m->ticks = 0; }

	/* Compute co-ordinates for each relative direction */
	switch(d1 = d) {
		case 0 :		/* Facing up */
			ax = x;		ay=y-1;
			bx = x;		by=y+1;
			lx = x-1;	ly=y;
			rx = x+1;	ry=y;
			break;
		case 1 :		/* Facing right */
			ax = x+1;	ay = y;
			bx = x-1;	by = y;
			lx = x;		ly = y-1;
			rx = x;		ry = y+1;
			break;
		case 2 :		/* Facing down */
			ax = x;		ay = y+1;
			bx = x;		by = y-1;
			lx = x+1;	ly = y;
			rx = x-1;	ry = y;
			break;
		case 3 :		/* Facing left */
			ax = x-1;	ay = y;
			bx = x+1;	by = y;
			lx = x;		ly = y+1;
			rx = x;		ry = y-1;
			break; }

	/* Test for trail & follow if found */
	switch(m->method) {
	case FR_HAND :
		if(maze[ay+1][ax+1] == TRAIL) goto move;
	case R_HAND :
		if(maze[ry+1][rx+1] == TRAIL) { ++d; goto move; }
		if(maze[ay+1][ax+1] == TRAIL) { goto move; }
		if(maze[ly+1][lx+1] == TRAIL) { d += 3; goto move; }
		break;
	case FL_HAND :
		if(maze[ay+1][ax+1] == TRAIL) goto move;
	case L_HAND :
		if(maze[ly+1][lx+1] == TRAIL) { d += 3; goto move; }
		if(maze[ay+1][ax+1] == TRAIL) { goto move; }
		if(maze[ry+1][rx+1] == TRAIL) { ++d; goto move; } }

	/* No trail, follow prescribed wandering pattern */
	switch(m->method) {
	case FR_HAND :
		if(maze[ay+1][ax+1] < WALL) break;
	case R_HAND :
		if(maze[ry+1][rx+1] < WALL) { ++d; break; }
		if(maze[ay+1][ax+1] < WALL) { break; }
		if(maze[ly+1][lx+1] < WALL) { d += 3; break; }
		d += 2; break;
	case FL_HAND :
		if(maze[ay+1][ax+1] < WALL) break;
	case L_HAND :
		if(maze[ly+1][lx+1] < WALL) { d += 3; break; }
		if(maze[ay+1][ax+1] < WALL) { break; }
		if(maze[ry+1][rx+1] < WALL) { ++d; break; }
		d += 2; }

move:
	x1 = x;
	y1 = y;
	switch(d &= 3) {
		case 0 : --y; break;
		case 1 : ++x; break;
		case 2 : ++y; break;
		case 3 : --x; }

	if(d != d1) {	/* If turning, show turn */
		wgotoxy(x1, y1); wputc(m->symbols[d]);
		delay(P/2); }

	/* Erase old cat, and then draw again in new position */
	wgotoxy(x1, y1);
	if(maze[y1+1][x1+1] == CHEESE)
		wputc(DCHEESE);
	else {
		wputc(' ');
		maze[y1+1][x1+1]=0; }
	wgotoxy(m->x = x, m->y = y); wputc(m->symbols[m->direction = d]);

	/* Check to see if we nabbed the mouse */
	if(((x == mx) && (y == my)) || ((x1 == mx) && (y1==my))) {
		m->stop = 10;
		mouse_hit(); }
}

/*
 * Move the mouse through the maze
 */
move_mouse()
{
	int x1, y1, i;
	static int oldir;
	static char symbols[] = { 0x1E, 0x10, 0x1F, 0x11 };

	/* Determine new position using direction */
	x1 = mx;
	y1 = my;
	switch(direction) {
		case 0 :	--y1;	break;
		case 1 :	++x1;	break;
		case 2 :	++y1;	break;
		case 3 :	--x1;	}

	/* If new position is occupied, continue with old direction */
	if(maze[y1+1][x1+1] >= WALL) {
		x1 = mx;
		y1 = my;
		switch(oldir) {
			case 0 :	--y1;	break;
			case 1 :	++x1;	break;
			case 2 :	++y1;	break;
			case 3 :	--x1;	} }
	else
		oldir = direction;

	/* If maze is open, move into the new position */
	if((i = maze[y1+1][x1+1]) < WALL) {
		wgotoxy(mx, my); wputc(DTRAIL);
		maze[my+1][mx+1] = TRAIL;
		mx = x1;
		my = y1; }
	wgotoxy(mx, my); wputc(symbols[direction]);

	/* If we found another cheese, munch it! */
	if(i == CHEESE) {
		if(!--num_cheese) {
			/* We beat this level */
			beep(2000, 500);
			beep(2500, 500);
			beep(2000, 500);
			if(passages < 100)
				passages += 10;
			if(cats < (sizeof(cat)/sizeof(struct cat)))
				++cats;
			else for(i = 0; i < cats; ++i) {
				if(cat[i].speed)
					--cat[i].speed; }
			longjmp(jbuffer, 0); }
		if(quiet)
			delay(P);
		else {
			beep(1000, P/2);
			beep(2000, P/2); } }
	else
		delay(P);

	/* Check to see if we wandered into a cat */
	for(i=0; i < cats; ++i) {
		if((mx == cat[i].x) && (my == cat[i].y) && !cat[i].stop) {
			cat[i].stop = 10;
			mouse_hit(); } }
}
